﻿using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using RabbitMQ.Client;

namespace smp.RabbitMq
{
    public class RabbitMqBusBase
    {
        protected readonly IRabbitMqConfiguration Configuration;
        private readonly IConnection _connection;
        protected readonly IModel Channel;
        internal readonly ILog Log;

        internal RabbitMqBusBase(IRabbitMqConfiguration configuration)
        {
            Log = InstancesFactory.Log;
            Configuration = configuration;

            var connectionFactory = SingletonFactory.Instance.GetConnectionFactory(Configuration);
            _connection = connectionFactory.CreateConnection();
            Log.Debug("Created RabbitMq connection to host : {0} and virtual host : {1}", configuration.Host, configuration.VirtualHost);

            Channel = _connection.CreateModel();
            Log.Debug("Created RabbitMq Channel on connection on host : {0} and virtual host : {1}", configuration.Host, configuration.VirtualHost);

            Channel.ExchangeDeclare(Configuration.Exchange, Configuration.ExchangeType.ToString(), Configuration.DurableExchange, Configuration.AutoDeleteExchange, null);
            Log.Debug("Declared exchange {0} on host {1} and virtual host {2}", Configuration.Exchange,Configuration.Host, Configuration.VirtualHost);

            if(string.IsNullOrEmpty(Configuration.Queue))
                return;

            var queueArgs = new Dictionary<string, object>();
            if (Configuration.ErrorQueue)
            {
                var errorExchange = Configuration.Queue + "_ERROR";
                Channel.ExchangeDeclare(errorExchange, Configuration.ExchangeType.ToString(), Configuration.DurableExchange, Configuration.AutoDeleteExchange, null);
                Log.Debug("Delclared Error exchange {0}", errorExchange);

                var errorQueue = Configuration.Queue + "_ERROR_QUEUE";
                Channel.QueueDeclare(errorQueue, Configuration.DurableQueue, false, Configuration.AutoDeleteQueue, null);
                Channel.QueueBind(errorQueue, errorExchange, "");
                Log.Debug("Declared Error Queue {1} and bound to error exchange {1}", errorQueue, errorExchange);

                queueArgs.Add("x-dead-letter-exchange", errorExchange);
                Log.Debug("Added dead letter error exchange parameters to subscription queue");
            }

            Channel.QueueDeclare(Configuration.Queue, Configuration.DurableQueue, false, Configuration.AutoDeleteQueue, queueArgs);
            Log.Debug("Declared queue {0}", Configuration.Queue);

            Channel.QueueBind(Configuration.Queue, Configuration.Exchange, Configuration.RoutingKey);
            Log.Debug("Bound Queue {0} to exhange {1} with routing key {2}", Configuration.Queue, Configuration.Exchange, Configuration.RoutingKey);

            Channel.BasicQos(0, 1, false);
        }

        protected bool Disposed;

        protected void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    if (Channel != null)
                    {
                        if (Channel.IsOpen)
                        {
                            Channel.Close();
                        }
                        //_channel.Dispose();
                    }

                    if (_connection != null)
                    {
                        if (_connection.IsOpen)
                        {
                            _connection.Close();
                        }
                        //_connection.Dispose();
                    }
                }
                Disposed = true;
            }
        }

        internal string SerializeMessage(object message)
        {
            return JsonConvert.SerializeObject(message);
        }

        internal Envelope SerializeMessage(Type messageType, object message)
        {
            var envelope = new Envelope();
            envelope.MessageType = messageType.AssemblyQualifiedName;
            envelope.Message = JsonConvert.SerializeObject(message);
            return envelope;
        }

        protected T DeserializeMessage<T>(string message)
        {
            return JsonConvert.DeserializeObject<T>(message);
        }

        internal object DeserializeMessage(string messageTypeAssemblyQualifiedName, string message)
        {
            var type = Type.GetType(messageTypeAssemblyQualifiedName);
            return JsonConvert.DeserializeObject(message, type);
        }

        internal Envelope GetEnvelopeFromPacket(byte[] packet)
        {
            var serializedEnvelope = Encoding.ASCII.GetString(packet);

            if (string.IsNullOrEmpty(serializedEnvelope))
            {
                Log.Debug("Receieved empty message on queue {0}", Configuration.Queue);
                return null;
            }

            Log.Debug("Recieved message {0} on queue {1}", serializedEnvelope, Configuration.Queue);

            var envelope = JsonConvert.DeserializeObject<Envelope>(serializedEnvelope);
            if (string.IsNullOrEmpty(envelope.MessageType))
            {
                Log.Error("Subscriber recieved an invalid envelope with due to null message type.");
                return null;
            }
            if (string.IsNullOrEmpty(envelope.Message))
            {
                Log.Error("Subscriber recieved an invalid envelope with no message package.");
                return null;
            }
            return envelope;
        }
    }
}
